/**
 * 凡是可以使用字符串或数值作为属性的地方，都可以使用符号。
 * 这就包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性。
 * 对象字面量只能在计算属性语法中使用符号作为属性。
 */
let s1 = Symbol('foo');
let s2 = Symbol('bar');
let s3 = Symbol('baz');
let s4 = Symbol('qux');

let o1 = {
  [s1]: 'foo val'
};
// 这样也可以 o[s1] = 'foo val';
console.log(o1); // { [Symbol(foo)]: 'foo val' }

let o2 = {};
Object.defineProperty(o2, s2, { value: 'bar val' });
console.log(o2);

let o3 = {};
Object.defineProperties(o3, {
  [s2]: { value: 'baz val' },
  [s3]: { value: 'qux val' }
});
console.log(Object.getOwnPropertySymbols(o3)); // [ Symbol(bar), Symbol(baz) ]

/**
 * 类似于 Object.getOwnPropertyNames()返回对象实例的常规属性数组，Object.getOwnPropertySymbols()返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。
 * Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象。
 * Reflect.ownKeys()会返回两种类型的键
 */
let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz prop',
  qux: 'qux prop'
};
console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(foo), Symbol(bar) ]
console.log(Object.getOwnPropertyNames(o)); // [ 'baz', 'qux' ]
console.log(Object.getOwnPropertyDescriptors(o)); // {  baz: {    value: 'baz prop',    writable: true,    enumerable: true,    configurable: true  },  qux: {    value: 'qux prop',    writable: true,    enumerable: true,    configurable: true  },[Symbol(foo)]: {    value: 'foo val',    writable: true,    enumerable: true,    configurable: true  },[Symbol(bar)]: {    value: 'bar val',    writable: true,    enumerable: true,    configurable: true  }}
console.log(Reflect.ownKeys(o)); // [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]

/**
 * 因为符号属性是对内存中符号的一个引用，所以直接创建并用作属性的符号不会丢失。
 * 但是，如果没有显式地保存对这些属性的引用，那么必须遍历对象的所有符号属性才能找到相应的属性键
 */
let o4 = {
  [Symbol('foo')]: 'foo val',
  [Symbol('bar')]: 'bar val'
};

console.log(o4); // { [Symbol(foo)]: 'foo val', [Symbol(bar)]: 'bar val' }
let barSymbol = Object.getOwnPropertySymbols(o4).find(symbol => symbol.toString().match(/bar/));
console.log(barSymbol); // Symbol(bar)
